/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8                               :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/fds.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/rlimit.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/mem/mem.h"
#include "libc/nt/accounting.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/processaccess.h"
#include "libc/nt/enum/startf.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/processinformation.h"
#include "libc/nt/struct/startupinfo.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/proc/describefds.internal.h"
#include "libc/proc/ntspawn.h"
#include "libc/runtime/internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/sysv/pib.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#if SupportsWindows()

__msabi extern typeof(CloseHandle) *const __imp_CloseHandle;
__msabi extern typeof(TerminateProcess) *const __imp_TerminateProcess;

void __sig_worker_lock(void);
void __sig_worker_unlock(void);

static bool IsDangerousArray(char *const strings[]) {
  if (kisdangerous(strings))
    return true;
  while (*strings)
    if (kisdangerous(*strings++))
      return true;
  return false;
}

textwindows static int sys_execve_nt_impl(const char *program,
                                          char *const argv[],
                                          char *const envp[],
                                          sigset_t sigmask) {

  struct CosmoPib *pib = __get_pib();

  // get parent pid
  int ppid_cosmo = sys_getppid_nt();
  int ppid_win32 = sys_getppid_nt_win32();

  // new process should be a child of our parent
  int64_t hParentProcess =
      !__vforked && ppid_win32
          ? OpenProcess(kNtProcessDupHandle | kNtProcessCreateProcess, false,
                        ppid_win32)
          : 0;

  // inherit sysdrive
  char drivevar[15] = "COSMOSDRIVE=C:";
  drivevar[12] = __getcosmosdrive();

  // inherit pid
  char pidvar[11 + 21];
  FormatUint64(stpcpy(pidvar, "_COSMO_PID="), pib->pid);

  // inherit parent process id
  char ppidvar[12 + 21 + 1 + 21 + 1], *p = ppidvar;
  p = stpcpy(p, "_COSMO_PPID=");
  p = FormatUint64(p, ppid_win32);
  *p++ = ':';
  p = FormatUint64(p, ppid_cosmo);

  // lock file descriptors
  __fds_lock();

  // define stdio handles for the spawned subprocess
  //
  // cosmo processes will inherit the true overlapped file descriptor
  // via $_COSMO_FDS_V2, so these handle will only be used by kprintf
  // and non-cosmo win32 programs which might not be equipped to deal
  // with overlapped i/o, so we shall re-open the file to remove that
  struct NtStartupInfo si = {
      .cb = sizeof(struct NtStartupInfo),
      .dwFlags = kNtStartfUsestdhandles,
      .hStdInput = -1,
      .hStdOutput = -1,
      .hStdError = -1,
  };
  for (int fd = 0; fd < 3; ++fd)
    if (__isfdopen(fd) && !__is_cloexec(pib->fds.p + fd))
      si.stdiofds[fd] = pib->fds.p[fd].handle;

  // which process is responsible for spawning the child?
  int64_t hCreatorProcess;
  if (hParentProcess) {
    hCreatorProcess = hParentProcess;
  } else {
    hCreatorProcess = GetCurrentProcess();
  }

  // pass serialized file descriptor table in environment
  char *fdspec;
  int64_t *lpExplicitHandles;
  uint32_t dwExplicitHandleCount;
  long extrahands[3] = {pib->hStopChurn, pib->hStopEvent, 0};
  fdspec =
      __describe_fds(pib->fds.p, pib->fds.n, extrahands, &si, hCreatorProcess,
                     &lpExplicitHandles, &dwExplicitHandleCount);
  __fds_unlock();
  if (!fdspec) {
    if (hParentProcess)
      __imp_CloseHandle(hParentProcess);
    return -1;
  }

  // posix requires that pending signals be passed along to the child we
  // need to shut down the signal worker thread in the background, so it
  // doesn't snipe our signals while the child is spawning to inherit it
  __sig_worker_lock();
  sigset_t sigpending = atomic_load(&__get_tls_win32()->tib_sigpending);
  if (pthread_orphan_np())
    sigpending |= atomic_load(pib->sigpending);
  sigset_t ignores = 0;
  for (int sig = 1; sig <= NSIG; ++sig)
    if (pib->sighandrvas[sig - 1] == (intptr_t)SIG_IGN)
      ignores |= 1ull << (sig - 1);
  char sigsvar[6 + 21];
  FormatUint64(stpcpy(sigsvar, "_SIGS="), sigpending);
  char maskvar[6 + 21];
  FormatUint64(stpcpy(maskvar, "_MASK="), sigmask);
  char ignsvar[6 + 21];
  FormatUint64(stpcpy(ignsvar, "_IGNS="), ignores);
  char stp1var[18 + 21];
  FormatUint64(stpcpy(stp1var, "_COSMO_STOP_CHURN="), extrahands[0]);
  char stp2var[19 + 21];
  FormatUint64(stpcpy(stp2var, "_COSMO_STOP_EVENT="), extrahands[1]);

  // inherit resource limits
  char rlimvar[RLIMIT_VAR_BUF_SIZE];
  __rlimit_serialize(rlimvar);

  // launch the process
  struct NtProcessInformation pi;
  int rc = ntspawn(&(struct NtSpawnArgs){
      AT_FDCWD, program, argv, envp,
      (char *[]){fdspec, sigsvar, maskvar, ignsvar, pidvar, ppidvar, drivevar,
                 stp1var, stp2var, rlimvar, 0},
      pib->dwCreationFlags, 0, hCreatorProcess, lpExplicitHandles,
      dwExplicitHandleCount, &si, &pi});
  __undescribe_fds(fdspec, hCreatorProcess, lpExplicitHandles,
                   dwExplicitHandleCount);
  __sig_worker_unlock();
  if (rc == -1) {
    if (hParentProcess)
      __imp_CloseHandle(hParentProcess);
    if (GetLastError() == kNtErrorSharingViolation) {
      return etxtbsy();
    } else {
      return -1;
    }
  }

  CloseHandle(pi.hThread);

  if (_weaken(sys_vfork_nt_exec))
    _weaken(sys_vfork_nt_exec)(pi.hProcess);

  // check if parent spoofing worked
  if (hParentProcess) {
    // give child to libc/proc/proc.c worker thread in parent
    int64_t handle;
    if (DuplicateHandle(GetCurrentProcess(), pi.hProcess, hParentProcess,
                        &handle, 0, false, kNtDuplicateSameAccess)) {
      unassert(!(handle & 0xFFFFFFFFFF000000));
      __imp_TerminateProcess(-1, 0x23000000u | handle);
    } else {
      // TODO(jart): Why does `make loc` print this?
      __imp_TerminateProcess(-1, ECHILD);
    }
    __builtin_unreachable();
  }

  // we couldn't reparent the new process
  STRACE("warning: execve() lingering due to non-cosmo parent process");

  // terminate other threads
  _pthread_lock();
  struct Dll *e;
  struct PosixThread *me = _pthread_self();
  for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
    struct PosixThread *pt = POSIXTHREAD_CONTAINER(e);
    if (pt->tib->tib_flags & TIB_FLAG_VFORKED)
      continue;  // TODO(jart): wait for vfork to die before exiting?
    if (pt == me)
      continue;
    TerminateThread(
        atomic_load_explicit(&pt->tib->tib_syshand, memory_order_relaxed),
        SIGKILL);
  }

  // just in case we killed a thread that was holding this lock which
  // would otherwise prevent TerminateThisProcess() from killing this
  __sig_wipe();

  // wait for child to terminate and propagate exit code
  for (;;) {
    uint32_t status;
    WaitForSingleObject(pi.hProcess, -1u);
    GetExitCodeProcess(pi.hProcess, &status);
    if (status != kNtStillActive) {
      if ((status & 0xFF000000u) == 0x23000000u) {
        // handle child execve()
        __imp_CloseHandle(pi.hProcess);
        pi.hProcess = status & 0x00FFFFFF;
      } else {
        // handle child _Exit()
        if (status == 0xc9af3d51u)
          status = kNtStillActive;
        TerminateThisProcess(status);
      }
    }
  }
}

textwindows int sys_execve_nt(const char *program, char *const argv[],
                              char *const envp[]) {
  int rc;
  if (kisdangerous(program) || IsDangerousArray(argv) || IsDangerousArray(envp))
    return efault();
  BLOCK_SIGNALS;
  rc = sys_execve_nt_impl(program, argv, envp, _SigMask);
  ALLOW_SIGNALS;
  return rc;
}

#endif /* __x86_64__ */
